/*
* ============================================================================
*  Name        : sea_timer_server.cpp
*  Part of     : Finite state machine framework 
*  Description : State machine timer implementation
*  Version     : %version: %
*  
*
* ============================================================================
*/
#include "sea_timer_server.h"

/**
 * @file sea_timer_server.cpp
 *
 * @ingroup StateMachineFramework
 */
using namespace sea;


// -------------------------------------------------------------------------------------------------
// Ctor
// -------------------------------------------------------------------------------------------------
TimerServer::TimerServer( ):
    m_alive( false )
{
}

// -------------------------------------------------------------------------------------------------
// Dtor
// -------------------------------------------------------------------------------------------------
TimerServer::~TimerServer( )
{
}

// -------------------------------------------------------------------------------------------------
// Start/restart a timer
// Executed in the context of a state machine
// -------------------------------------------------------------------------------------------------
void TimerServer::start_timer( Timer* timer )
{
    ASSERT_DEBUG( timer );
    
    // Dont bother with timers with heart rate 0
    if ( !timer->heart_beat_rate( ) )
    {
        return;
    }

    OsMutex::Scoped safe( &m_lock );

    if ( m_timers.size( ) == 0 ) 
    {
        // For the first timer start timestamp is 0
        timer->set_start_time_stamp( 0 );

        // Register it with timer server
        m_timers.attach_first( timer );
        ASSERT_DEBUG( m_timers.size( ) > 0 );

        // Start counting on the first registered timer 
        m_os_time.start( );
    }
    else 
    {
        int starttimestamp = m_os_time.elapsed( );
    
        // Sort by expiration time
        bool inserted = false;

        for( HIterator< Timer > it( m_timers.get_first( ).get( ) ); *it; it++ )
        {
            Timer* the_timer = *it;

            if ( the_timer->get_start_time_stamp( ) + the_timer->heart_beat_rate( ) >
                starttimestamp + timer->heart_beat_rate( ) )
            {
                timer->set_start_time_stamp( starttimestamp );

                m_timers.insert_left_of( timer, the_timer );

                inserted = true;
                break;
            }
        }

        if ( !inserted )
        {
            m_timers.push( timer );
        }
    }


    // Check if timer is the first one in the list - trigger timer server to 
    // wait on it
    if ( m_timers.peak( ) == timer )
    {
        ASSERT_DEBUG( m_timers.size( ) > 0 );
        wakeup( );
    }
}

// -------------------------------------------------------------------------------------------------
// Stop/cancel timer
// Executed in the context of a state machine
// -------------------------------------------------------------------------------------------------
void TimerServer::stop_timer( Timer* timer )
{
    ASSERT_DEBUG( timer );
    
    bool server_update = false;

    {
        OsMutex::Scoped safe( &m_lock );

        if ( m_timers.size( ) == 0 )
        {
            return;
        }

        // Check if timer to be deleted is the first one in the list, that is server waits on it
        if ( m_timers.peak( ) == timer )
        {
            server_update = true;
        }

        // Remove timer from the active list
        m_timers.detach( timer );
    }

    if ( server_update )
    {
        // Since we removed the very first timers
        // wakeup timer server to update others
        wakeup( );
    }
}

// -------------------------------------------------------------------------------------------------
// Update all active timers listed with the server
// Executed in the context of the timer server
// -------------------------------------------------------------------------------------------------
SEA_INLINE void TimerServer::update( )
{
    Queue fired;

    {
        OsMutex::Scoped safe( &m_lock );
        int size = m_timers.size( );

        if ( size == 0 )
        {
            return;
        }

        sea::ref< Timer > timer;
        for( int i = 0; i < size; ++i )
        {
            timer = m_timers.peak( ).get( );
            if ( timer == NULL || timer->get_start_time_stamp( ) + timer->heart_beat_rate( ) > m_os_time.elapsed( ) ) 
            {
                break;
            }

            timer = m_timers.pop( ).get( );
            timer->m_state = Timer::TO_BE_FIRED;

            ASSERT_DEBUG( timer != NULL );
            fired.push( timer.get( ) );
        }

        // Handle system timer wrap around
        timer = m_timers.get_last( ).get( );

        if ( timer != NULL && ( timer->get_start_time_stamp( ) + timer->heart_beat_rate( ) >= K_MAX_TIMER_MS ) )
        {
            int current = m_os_time.elapsed( );

            // Restart the "system" timer
            m_os_time.start( );

            // Update all timers
            for( HIterator< Timer > it( m_timers.get_first( ).get( ) ); *it; it++ ) 
            {
                // Calculate remaining sleep time
                // Example:
                //  3    = 10                      + 5                      - 12
                int time = it->get_start_time_stamp( ) + it->heart_beat_rate( ) - current;
                // Set updates start time
                //  -2              = 3    - 5
                it->set_start_time_stamp( time - it->heart_beat_rate( ) );
            }
        }
    }

    // At this very moment timer server update can be interrupted by the state machines
    // re-activating the timers. For that reaon we must have a m_state protected by
    // the timer lock itself and being checked in the invoke_callback( ) below

    // Invoke callbacks for expired timers
    for( ;; )
    {
        sea::ref< Timer > timer;
        {
            OsMutex::Scoped safe( &m_lock );
            timer = fired.pop( ).get( );
            if ( timer == NULL )
            {
                break;
            }
        }
        timer->invoke_callback( );
    }
}

// -------------------------------------------------------------------------------------------------
// Terminate timer server
// -------------------------------------------------------------------------------------------------
void TimerServer::terminate( ) 
{
    {
        OsMutex::Scoped safe( &m_lock );
        if ( !m_alive )
        {
            return;
        }
        m_timers.clear( );

        m_alive = false;
        m_signal.wakeup( );
    }

    // Wait for the server to finish
    wait( );
}

// -------------------------------------------------------------------------------------------------
// Main routine/thread
// -------------------------------------------------------------------------------------------------
void TimerServer::exec( )
{
    m_alive = true;
    for ( ;; ) 
    {
        sea::ref< Timer > timer;
        int sleepTime = 0;
        {
            OsMutex::Scoped safe( &m_lock );
            timer = m_timers.peak( ).get( );
            if ( timer != NULL )
            {
                // Calculate remaining sleep time
                //  3     = -2                          + 5                          - 0
                sleepTime = timer->get_start_time_stamp( ) + timer->heart_beat_rate( ) - 
                    m_os_time.elapsed( );
            }
        }

        // If no active timers - go to sleep forever
        if ( timer == NULL )
        {
            sleep( -1 );
        }
        else
        if ( sleepTime > 0 )
        {
            sleep( sleepTime );
        }
        else
        {
            // Time is overdue - do update ASAP
        }

        if ( !m_alive )
        {
            break;
        }
        update( );
    }
}

// -------------------------------------------------------------------------------------------------
